package com.bluexmicro.example.page.main

import android.bluetooth.BluetoothDevice
import android.content.Context
import android.util.Log
import android.widget.Toast
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringSetPreferencesKey
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.bluexmicro.android.coroutineble.toHexString
import com.bluexmicro.android.ota.CoroutineOta
import com.bluexmicro.android.ota.OtaParamHelper
import com.bluexmicro.android.ota.entity.OtaTask
import com.bluexmicro.example.dataStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.io.File
import java.io.Serializable
import java.math.BigInteger
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.zip.CRC32

class MainViewModel : ViewModel() {

    init {
        val crc32 = CRC32()
        crc32.update(
            byteArrayOf(
                0xDF.toByte(),
                0xEA.toByte(),
                0x35,
                0x92.toByte(),
                0x02,
                0x00,
                0x00,
                0x00,
                0x00,
                0x10,
                0x00,
                0x00,
                0x00,
                0x00,
                0x10,
                0x00
            )
        )
        Log.e("requestStart: ", "${crc32.value.toString(16)}")
    }

    val fastMode = mutableStateOf(false)

    val selectedDevice = mutableStateOf<BluetoothDevice?>(null)

    val tasks = mutableStateListOf<OtaTaskModel>()

    val uiEnable = mutableStateOf(true)

    fun selectFile(context: Context, file: File, address: BigInteger?) =
        viewModelScope.launch(Dispatchers.Default) {
            val rawData = OtaParamHelper.loadBinData(file) ?: return@launch // todo 告知文件无效
            val taskModel = OtaTaskModel(file, address, rawData)
            if (tasks.isEmpty()) {
                tasks.add(taskModel)
            } else {
                var index = -1
                for (withIndex in tasks.withIndex()) {
                    if (withIndex.value.file.path == file.path) {
                        index = withIndex.index
                        break
                    }
                }
                if (index in tasks.indices) {
                    tasks[index] = taskModel
                } else {
                    tasks.add(taskModel)
                }
            }
            saveTasks(context)
        }

    fun removeFile(context: Context, element: OtaTaskModel) {
        tasks.remove(element)
        saveTasks(context)
    }

    ///////////////////////////////////////////////////////////////////////////
    // start
    ///////////////////////////////////////////////////////////////////////////

    fun startUpdate(context: Context) = viewModelScope.launch {
        val device = selectedDevice.value ?: return@launch
        uiEnable.value = false
        val mTasks = tasks.map {
            OtaTask(it.rawData, it.address)
        }
        val mode = fastMode.value
        val coroutineOta = CoroutineOta(context, device)
        try {
            setTaskModelStatus(TaskStatus.WAITING, true)
            coroutineOta.startTransaction(mode)
            for ((index, task) in mTasks.withIndex()) {
                val taskModel = tasks[index]
                taskModel.status.value = TaskStatus.UPDATING
                taskModel.startTimeMillis = System.currentTimeMillis()
                var err: String? = null
                coroutineOta.execute(task, mode).catch {
                    err = it.message
                }.collect {
                    taskModel.progress.value = it
                }
                taskModel.endTimeMillis = System.currentTimeMillis()
                taskModel.errorMessage = err
                taskModel.description = if (err == null) {
                    val seconds = (taskModel.endTimeMillis - taskModel.startTimeMillis) / 1000
                    ": 成功：耗时${seconds}秒"
                } else {
                    ": 失败：$err"
                }
                taskModel.status.value = TaskStatus.FINISH
            }
        } catch (e: Exception) {
            e.printStackTrace()
            toast(context, e.message ?: "Unknown Error")
        }
        coroutineOta.endTransaction(mode)
        delay(1000)
        setTaskModelStatus(TaskStatus.IDLE)
        uiEnable.value = true
    }

    private fun toast(context: Context, msg: String) = viewModelScope.launch(Dispatchers.Main) {
        Toast.makeText(context, msg, Toast.LENGTH_LONG).show()
    }

    private fun setTaskModelStatus(status: TaskStatus, clearCache: Boolean = false) {
        for (task in tasks) {
            task.status.value = status
            if (clearCache) {
                task.startTimeMillis = 0L
                task.endTimeMillis = 0L
                task.description = ""
                task.progress.value = 0f
                task.errorMessage = null
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // 策略是：
    // 第一次启动时加载一次
    // 每次更改就保存一次
    ///////////////////////////////////////////////////////////////////////////

    private val taskKey = stringSetPreferencesKey("tasks")
    private val fastModeKey = booleanPreferencesKey("fastMode")

    private fun loadTasksFlow(context: Context): Flow<List<LocalFile>> =
        context.dataStore.data.map {
            val sets = mutableListOf<LocalFile>()
            it[taskKey]?.forEach { formatTask ->
                //"path|hexAddress?"
                val params = formatTask.split('|')
                val file = if (0 in params.indices) {
                    File(params[0])
                } else {
                    null
                }
                val address = if (1 in params.indices) {
                    try {
                        BigInteger(params[1], 16)
                    } catch (e: Exception) {
                        e.printStackTrace()
                        null
                    }
                } else {
                    null
                }
                if (file != null) {
                    sets.add(LocalFile(file, address))
                }
            }
            sets
        }

    private fun loadFastModeFlow(context: Context): Flow<Boolean> = context.dataStore.data.map {
        it[fastModeKey] ?: false
    }

    private var hadLoadTask = false
    fun loadLocalTasks(context: Context) = viewModelScope.launch(Dispatchers.IO) {
        if (hadLoadTask) return@launch
        tasks.clear()
        loadTasksFlow(context).firstOrNull()?.let {
            val tmp = mutableListOf<OtaTaskModel>()
            for (local in it) {
                val raw = OtaParamHelper.loadBinData(local.file) ?: continue
                tmp.add(OtaTaskModel(local.file, local.address, raw))
            }
            hadLoadTask = true
            tasks.addAll(tmp)
        }
    }

    fun loadLocalFastMode(context: Context) = viewModelScope.launch(Dispatchers.IO) {
        fastMode.value = loadFastModeFlow(context).firstOrNull() ?: false
    }

    fun saveFastMode(context: Context, fastMode: Boolean) = viewModelScope.launch(Dispatchers.IO) {
        context.dataStore.edit { settings ->
            settings[fastModeKey] = fastMode
        }
        if (!fastMode && tasks.size > 1) {
            tasks.removeRange(fromIndex = 1, tasks.size)
        }
        saveTasks(context)
    }

    private fun saveTasks(context: Context) = viewModelScope.launch(Dispatchers.IO) {
        val list = tasks.map {
            "${it.file.path}|${it.address?.toString(16)}"
        }
        context.dataStore.edit { settings ->
            settings[taskKey] = list.toSet()
        }
    }
}

class OtaTaskModel(
    val file: File,
    val address: BigInteger?,
    val rawData: ByteArray
) {

    var startTimeMillis = 0L
    var endTimeMillis = 0L
    var errorMessage: String? = null

    val progress = mutableStateOf(0f)
    val status = mutableStateOf(TaskStatus.IDLE)
    var description = ""

}

enum class TaskStatus {
    IDLE,//未开始
    WAITING,//开始OTA了，但该任务正在等待前面的任务
    UPDATING,
    FINISH,//OTA中，该任务已结束，还有别的任务正在进行中
}

data class LocalFile(
    val file: File,
    val address: BigInteger?
) : Serializable